﻿using System;
using Bettzueche.RLGlue.Generics;
using System.Diagnostics;

namespace Bettzueche.RLGlue.Codec.Network {

    /// <summary>
    /// Abstract Agent Middleware.
    /// </summary>
    /// <seealso cref="Bettzueche.RLGlue.Codec.Network.ClientAgent"/>
    /// <seealso cref="Bettzueche.RLGlue.Codec.Network.GenericClientAgent"/>
    public abstract class AbstractClientAgent : IClientAgent {
        /// <summary>Predefined String "Unknown Message: ".</summary>
        protected const String kUnknownMessage = "Unknown Message: ";
        /// <summary>network component for serialized communication with RLGlue.exe</summary>
        protected Network network;
        /// <summary>Reference to Agent Implementation.</summary>
        protected AnAgent agent;
        /// <summary>Flag that indicates the running experiment to stop</summary>
        protected bool killedFromLocalProcess = false;


        #region IClientAgent Members

        /// <summary>
        /// Kill Agent in RLGlue-context.
        /// </summary>
        /// <remarks>
        /// Original Beschreibung aus Java-RLGlue:<para>
        /// If you are using ClientAgent in a local context (like from Matlab)
        ///  this allows us to kill the agent without quitting matlab
        /// </para>
        /// </remarks>
        public virtual void KillProcess() {
            killedFromLocalProcess = true;
        }

        /// <summary>
        /// Close connection to RLGlue.exe.
        /// </summary>
        public virtual void Close() {
            network.Close();
        }

        /// <summary>
        /// Starts The agent for running in RLGlue context.
        /// </summary>
        public virtual void RunAgentEventLoop() {
            int agentState = 0;
            int dataSize = 0;
            int recvSize = 0;
            int remaining = 0;

            do {
                network.ClearRecvBuffer();

                try {
                    recvSize = network.Recv(8) - 8; // We may have received the header and part of the payload
                    // We need to keep track of how much of the payload was recv'd

                    agentState = network.GetInt(0);
                    dataSize = network.GetInt(Protocol.kIntSize);

                    remaining = dataSize - recvSize;
                    if (remaining < 0) {
                        Debug.WriteLine("Remaining was less than 0!");
                        remaining = 0;
                    }
                }
                catch (RLGlueException e) {
                    Trace.Fail("Failed to start Agent.", e.Message);
                    Environment.FailFast("Failed to start Agent.", e);
                }

                network.ClearRecvBuffer();
                int amountReceived = network.Recv(remaining);

                network.FlipRecvBuffer(); // sets Position to 0 and Length to 8 !?!

                // We have already received the header, now we need to discard it.
                //network.GetInt();
                //network.GetInt();

                switch (agentState) {
                    case Protocol.kAgentInit:
                        onAgentInit();
                        break;

                    case Protocol.kAgentStart:
                        onAgentStart();
                        break;

                    case Protocol.kAgentStep:
                        onAgentStep();
                        break;

                    case Protocol.kAgentEnd:
                        onAgentEnd();
                        break;

                    case Protocol.kAgentCleanup:
                        onAgentCleanup();
                        break;

                    case Protocol.kAgentMessage:
                        onAgentMessage();
                        break;

                    case Protocol.kRLTerm:
                        break;

                    default:
                        Trace.Fail(kUnknownMessage + agentState);
                        Environment.FailFast("Error while running the Agent. " + kUnknownMessage + agentState);
                        break;
                }

                network.FlipSendBuffer();
                network.Send(); //Daten wurden in den Handlern hinzugefügt
            } while (agentState != Protocol.kRLTerm && !killedFromLocalProcess);
        }

        /// <summary>
        /// Connecting with RLGLue.exe
        /// </summary>
        /// <param name="host"></param>
        /// <param name="port"></param>
        /// <param name="timeout"></param>
        /// <remarks>
        /// Called by agentloader.
        /// </remarks>
        public virtual void Connect(String host, int port, int timeout)/* throws Exception*/ {
            network.Connect(host, port, timeout);
            network.ClearSendBuffer();
            network.PutInt(Protocol.kAgentConnection);
            network.PutInt(0); // No body to this packet
            network.FlipSendBuffer();
            network.Send();
        }

        #endregion

        #region RMI handlers

        /// <summary>
        /// Handler for a AgentMessage message from RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Read out Parameters (message string) and call agent Method.
        /// Prepare confirmation of the call with return value (reply string) for sending.
        /// </remarks>
        protected abstract void onAgentMessage();

        /// <summary>
        /// Handler for a AgentCleanup message from RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Read out Parameters (void) omitted. Call Agent Methode.
        /// Prepare confirmation of AgentCleanup with return (void) for sending.
        /// </remarks>
        protected abstract void onAgentCleanup();

        /// <summary>
        /// Handler for a AgentEnd message from RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Read out Parameters (Reward) and call agent Method.
        /// Prepare confirmation of AgentStep with return (void) for sending.
        /// </remarks>
        protected abstract void onAgentEnd();

        /// <summary>
        /// Handler for a AgentStep message from RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Read out Parameters (Reward) and call agent Method.
        /// Prepare Confirmation of AgentStep with return value (Action) for sending.
        /// </remarks>
        protected abstract void onAgentStep();

        /// <summary>
        /// Handler for a AgentStart message from RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Read out  Parameters (Observation) and call agent Method.
        /// Prepare confirmation of AgentStart with returned action for sending.
        /// </remarks>
        protected abstract void onAgentStart();

        /// <summary>
        /// Handler for a AgentInit message from RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// read out Parameters (task_spec) and call agent Method.
        /// Prepare confirmation the call with returnvalue (void) for sending.
        /// </remarks>
        protected abstract void onAgentInit();

        #endregion

    }
}
